<template>
  <el-row>
    <template v-if="label || icons">
      <el-col v-if="label" :span="2" class="vertical-center"> {{ label }} </el-col>
      <el-col v-if="isScalar(modelValue)" :span="4" class="vertical-center toolbar-icons">
        <el-icon color="#F56C6C" @click="remove"><i-ep-minus /></el-icon>
        <el-icon color="#409EFF" @click="setType(VarType.NUMBER)"><i-ep-operation /></el-icon>
        <el-icon color="#909399" @click="setType(VarType.BOOLEAN)"><i-ep-select /></el-icon>
        <el-icon color="#67C23A" @click="setType(VarType.STRING)"><i-ep-tickets /></el-icon>
      </el-col>
      <el-col v-else-if="Array.isArray(value)" :span="4" class="vertical-center toolbar-icons">
        <el-icon color="#F56C6C" @click="remove"><i-ep-minus /></el-icon>
        <el-icon color="#E6A23C" @click="arrayAdd"><i-ep-plus /></el-icon>
        <el-icon @click="oc"><i-ep-arrow-right /></el-icon>
      </el-col>
      <el-col v-else :span="4" class="vertical-center toolbar-icons">
        <el-icon color="#F56C6C" @click="remove"><i-ep-minus /></el-icon>
        <el-icon color="#E6A23C" @click="objectAdd(VarType.OBJECT)"><i-ep-plus /></el-icon>
        <el-icon color="#409EFF" @click="objectAdd(VarType.NUMBER)"><i-ep-operation /></el-icon>
        <el-icon color="#909399" @click="objectAdd(VarType.BOOLEAN)"><i-ep-select /></el-icon>
        <el-icon color="#67C23A" @click="objectAdd(VarType.STRING)"><i-ep-tickets /></el-icon>
        <el-icon @click="oc"><i-ep-arrow-right /></el-icon>
      </el-col>
    </template>
    <el-col :span="18">
      <el-input v-if="type_ === VarType.STRING" v-model="value" />
      <el-input-number v-else-if="type_ === VarType.NUMBER" v-model="value" />
      <el-checkbox v-else-if="type_ === VarType.BOOLEAN" v-model="value" />
      <template v-else-if="Array.isArray(value) && display">
        <json-editor
          v-for="(v, index) in value"
          :key="index"
          class="array-row"
          :model-value="v"
          icons
          @update:model-value="arrayModify(index, $event)"
          @remove="arrayRemove(index)"
        />
      </template>
      <template v-else-if="display">
        <json-editor
          v-for="(v, l) in value"
          :key="l"
          :model-value="v"
          :label="'' + l"
          @update:model-value="objectModify('' + l, $event)"
          @remove="onRemove"
        />
      </template>
    </el-col>
  </el-row>
</template>

<script lang="ts" setup>
  import { computed, ref } from 'vue';
  import { ElMessageBox } from 'element-plus';

  enum VarType {
    STRING,
    NUMBER,
    BOOLEAN,
    ARRAY,
    OBJECT
  }

  function getTypeOf(v: unknown) {
    if (Array.isArray(v)) return VarType.ARRAY;
    if (null === v) return VarType.STRING;
    switch (typeof v) {
      case 'object':
        return VarType.OBJECT;
      case 'boolean':
        return VarType.BOOLEAN;
      case 'number':
        return VarType.NUMBER;
      default:
        return VarType.STRING;
    }
  }

  function isScalar(v: unknown): boolean {
    return ['number', 'string', 'boolean'].indexOf(typeof v) !== -1;
  }

  const props = defineProps<{
    modelValue: unknown;
    label?: string;
    icons?: boolean;
  }>();

  const type_ = computed(() => getTypeOf(props.modelValue));

  const emit = defineEmits(['update:modelValue', 'remove']);

  const display = ref(true);

  const value = computed({
    get() {
      return props.modelValue;
    },
    set(v) {
      emit('update:modelValue', v);
    }
  });

  function arrayModify(index: number, v: unknown): void {
    (value.value as Array<unknown>).splice(index, 1, v);
  }
  function arrayAdd(): void {
    let v = '';
    const first = (value.value as Array<unknown>)[0];
    if (typeof first !== 'undefined') v = JSON.parse(JSON.stringify(first));
    (value.value as Array<unknown>).push(v);
  }
  function arrayRemove(index: number): void {
    (value.value as Array<unknown>).splice(index, 1);
  }
  function objectModify(index: string, v: unknown): void {
    (value.value as Record<string, unknown>)[index] = v;
  }
  async function objectAdd(type: VarType): Promise<void> {
    let name = '';
    try {
      name = (
        await ElMessageBox.prompt('请输入键名', '提示', {
          confirmButtonText: '好了',
          cancelButtonText: '算了',
          inputPattern: /[a-zA-z][a-zA-z\d]*/,
          inputErrorMessage: '必须是标识字符串'
        })
      ).value;
    } catch (e) {
      return;
    }
    let v;
    switch (type) {
      case VarType.BOOLEAN:
        v = false;
        break;
      case VarType.OBJECT:
        v = {};
        break;
      case VarType.NUMBER:
        v = 0;
        break;
      default:
        v = '';
    }
    (value.value as Record<string, unknown>)[name] = v;
  }
  function remove(): void {
    if (props.label) emit('remove', props.label);
  }
  function onRemove(label: string): void {
    delete (value.value as Record<string, unknown>)[label];
  }
  function oc(): void {
    display.value = !display.value;
  }
  function setType(type: VarType) {
    switch (type) {
      case VarType.BOOLEAN:
        value.value = !!value.value;
        break;
      case VarType.NUMBER:
        value.value = Number(value.value);
        break;
      default:
        value.value = '' + value.value;
        break;
    }
  }
</script>

<script lang="ts">
  // 声明额外的选项
  // noinspection JSUnusedGlobalSymbols
  export default {
    inheritAttrs: false
  };
</script>

<style scoped>
  /*noinspection ALL*/
  .el-row {
    margin-bottom: 10px;
  }

  .vertical-center {
    display: flex;
    align-items: center;
  }

  /*noinspection ALL*/
  .toolbar-icons .el-icon {
    margin-left: 3px;
    font-size: 16px;
  }

  /*noinspection ALL*/
  .el-row:last-child {
    margin-bottom: 0;
  }

  .array-row {
    margin-bottom: 20px;
  }
</style>
